package controllers

import (
	"encoding/json"
	"fmt"
	"strings"
	"time"

	"github.com/astaxie/beego"
	"github.com/astaxie/beego/orm"
	"k8s.io/apimachinery/pkg/util/sets"

	"cvevulner/common"
	"cvevulner/errcode"
	"cvevulner/models"
	"cvevulner/taskhandler"

	"github.com/astaxie/beego/logs"
)

type CveIssueInfoController struct {
	BaseController
}

type CveIssueInfoData struct {
	IssueNum       string  `json:"issue_id"`
	CveNum         string  `json:"CVE_num"`
	OpeneulerScore float64 `json:"openeuler_score"`
	NvdScore       float64 `json:"NVD_score"`
	Branch         string  `json:"milestone"`
}

type CveIssueUpdate struct {
	CveId       string `json:"cve_id"`
	PackageName string `json:"package_name"`
}

const url = "https://gitee.com/%s/%s/issues/%s"

// @Title Get cveissueinfo
// @Description get cve
// @Param	status	int	true (0,1,2)
// @Success 200 {object} CveIssueInfoData
// @Failure 403 :status is err
// @router / [get]
func (u *CveIssueInfoController) Get() {
	req := u.Ctx.Request
	addr := req.RemoteAddr
	logs.Info("Method: ", req.Method, "Client request ip address: ", addr,
		", Header: ", req.Header, ", body: ", req.Body)
	resp := make(map[string]interface{})

	currentPage, err := u.GetInt("currentPage", 1)
	if err != nil {
		logs.Error("err: ", err, ", currentPage: ", currentPage)
		u.paramError(resp)
	}
	pageSize, err := u.GetInt("pageSize", 100)
	if err != nil {
		logs.Error("err: ", err, ", pageSize: ", pageSize)
		u.paramError(resp)
	}

	milestone := u.GetString("milestone", "")
	count := models.QueryIssueCount(milestone)
	if count > 0 {
		resp["total"] = count
		issueData, issueErr := models.QueryCveIssueByBranch(currentPage, pageSize, milestone)
		var ird = make([]CveIssueInfoData, len(issueData))
		if issueErr == nil && len(issueData) > 0 {
			for i, issues := range issueData {
				ird[i] = CveIssueInfoData{
					CveNum:         issues.CveNum,
					IssueNum:       issues.IssueNum,
					OpeneulerScore: issues.OpeneulerScore,
					NvdScore:       issues.NvdScore,
					Branch:         issues.AffectProduct,
				}
			}
			resp["body"] = ird
			u.success(resp)
		}
	}

	u.noData(resp, nil)
}

func (u *CveIssueInfoController) Post() {
	var req []CveIssueUpdate

	err := json.Unmarshal(u.Ctx.Input.RequestBody, &req)
	if err != nil {
		logs.Error("update cve issue score fail, err: ", err)
		return
	}

	if len(req) == 0 {
		return
	}

	logs.Info("updates cve issue :", req)

	for _, c := range req {
		cve := models.VulnCenter{CveNum: c.CveId, RepoName: c.PackageName, OrganizationID: 1}
		err = models.GetVulnCenterByCid(&cve, "CveNum", "RepoName", "OrganizationID")
		if err != nil {
			logs.Error("get vulCenter failed:", err)
			continue
		}

		err = taskhandler.UpdateCveIssueScore(cve)
		if err != nil {
			logs.Error("update cve score failed, err:", err)
		}
	}

}

type CveAllIssueController struct {
	BaseController
}

type CveAllIssueoData struct {
	Status              int8    `json:"status"`
	IssueNum            string  `json:"issue_id"`
	CveNum              string  `json:"CVE_num"`
	OpeneulerScore      float64 `json:"openeuler_score"`
	NvdScore            float64 `json:"NVD_score"`
	Branch              string  `json:"milestone"`
	CveLevel            string  `json:"CVE_level"`
	PlanClosedTime      string  `json:"plan_closed_time"`
	CvePublicTime       string  `json:"CVE_public_time"`
	CveVtopicPublicTime string  `json:"CVE_vtopic_public_time"`
	CveVtopicRecTime    string  `json:"CVE_vtopic_rec_time"`
	RpmPublicTime       string  `json:"rpm_public_time"`
	SaPublicTime        string  `json:"SA_public_time"`
	Version             string  `json:"version"`
	Component           string  `json:"component"`
	Create              string  `json:"create_time,omitempty"`
	Url                 string  `json:"issue_url,omitempty"`
	User                string  `json:"user,omitempty"`
	IsIgnore            int8    `json:"is_ignore"`
	FirstPatchTime      string  `json:"first_patch_time"`
	AffectUpdate        int8    `json:"affect_update"`
	PackageTime         string  `json:"package_time"`
}

// @Title Obtain all cve data and provide operation kanban for use
// @Description get issue
// @Param	pageSize 1,currentPage 10, updatetime nil
// @Success 200 {object} CveAllIssueoData
// @Failure 403 :status is err
// @router / [get]
func (u *CveAllIssueController) Get() {
	req := u.Ctx.Request
	addr := req.RemoteAddr
	logs.Info("Method: ", req.Method, "Client request ip address: ", addr,
		", Header: ", req.Header, ", body: ", req.Body)
	resp := make(map[string]interface{})

	communityFlag, flagErr := u.GetInt("communityFlag", 0)
	if flagErr != nil {
		communityFlag = 0
	}

	cve := u.GetString("cveNums")
	if cves := strings.Split(cve, ","); cve != "" && len(cves) > 0 {
		u.cveForIssue(cves, communityFlag)
	}

	if conditions := u.GetString("conditions"); len(conditions) != 0 {
		u.cvePackForIssue(conditions, communityFlag)
	}

	startTime := u.GetString("startTime", "")

	currentPage, err := u.GetInt("currentPage", 1)
	if err != nil {
		u.paramError(resp)
	}
	pageSize, err := u.GetInt("pageSize", 100)
	if err != nil {
		u.paramError(resp)
	}

	count := models.QueryCveAllIssueCount(communityFlag, startTime)
	if count > 0 {
		resp["total"] = count
		issueData, issueErr := models.QueryCveAllIssueData(currentPage, pageSize, communityFlag, startTime)
		if issueErr == nil && len(issueData) > 0 {
			var cid = make([]CveAllIssueoData, len(issueData))
			for i, issues := range issueData {
				var rpmPub string
				if len(issues.RpmReleaseTime) > 2 {
					rpmPub = issues.RpmReleaseTime
				} else {
					rpmPub = issues.SaReleaseTime
				}

				cid[i] = CveAllIssueoData{
					Status:              issues.Status,
					CveNum:              issues.CveNum,
					IssueNum:            issues.IssueNum,
					Version:             issues.OwnedVersion,
					Component:           issues.Repo,
					OpeneulerScore:      issues.OpeneulerScore,
					NvdScore:            issues.NvdScore,
					Branch:              issues.AffectedVersion,
					CveLevel:            issues.CveLevel,
					CveVtopicPublicTime: issues.FirstPerTime,
					CveVtopicRecTime:    issues.FirstGetTime,
					Create:              issues.CreateTime.Format("2006-01-02 15:04:05"),
					Url:                 fmt.Sprintf(url, issues.Owner, issues.Repo, issues.IssueNum),
					PlanClosedTime:      taskhandler.CvePlanCloseTime(issues.CreateTime, issues.CveLevel, false, false),
					CvePublicTime:       issues.CpublicTime,
					SaPublicTime:        issues.SaReleaseTime,
					RpmPublicTime:       rpmPub,
					User:                issues.IssueCreate,
					IsIgnore:            issues.IsIgnore,
					FirstPatchTime:      models.QueryFirstPatchTime(issues.CveNum),
					AffectUpdate:        issues.AffectUpdate,
					PackageTime:         issues.PackageTime,
				}
			}
			resp["body"] = cid
		}

		u.success(resp)
	}

	u.noData(resp, nil)
}

func (u *CveAllIssueController) cveForIssue(cves []string, communityFlag int) {
	resp := make(map[string]interface{})
	issueData, issueErr := models.QueryCveIssue(cves, communityFlag)
	if issueErr != nil {
		u.noData(resp, issueErr)
	}
	var res = make([]CveAllIssueoData, len(issueData))
	for i, issues := range issueData {
		var irda = CveAllIssueoData{
			CveNum:    issues.CveNum,
			IssueNum:  issues.IssueNum,
			Version:   issues.OwnedVersion,
			Component: issues.OwnedComponent,
			Create:    issues.CreateTime.Format("2006-01-02 15:04:05"),
			Url:       fmt.Sprintf(url, issues.Owner, issues.Repo, issues.IssueNum),
			User:      issues.IssueCreate,
		}

		_, accessToken := common.GetOwnerAndToken("", issues.OrganizateId)
		err, issueBody := taskhandler.GetGiteeIssue(accessToken, issues.Owner, issues.Repo, issues.IssueNum)
		if err == nil && issueBody != nil {
			if s, ok := issueBody["created_at"].(string); ok {
				if t, err := time.Parse("2006-01-02T15:04:05+08:00", s); err == nil {
					irda.Create = t.Format("2006-01-02 15:04:05")
				}
			}

			if u, ok := issueBody["user"].(map[string]interface{}); ok {
				if name, ok := u["name"].(string); ok {
					irda.User = name
				}
			}
		}
		res[i] = irda
	}

	resp["body"] = res

	u.success(resp)
}

func (u *CveAllIssueController) cvePackForIssue(conditions string, communityFlag int) {
	resp := make(map[string]interface{})
	var req []models.Cve
	err := json.Unmarshal([]byte(conditions), &req)
	if err != nil {
		u.noData(resp, err)
	}
	issueData, issueErr := models.QueryCvePackIssue(req, communityFlag)
	if issueErr != nil {
		u.noData(resp, nil)
	}

	var res = make([]CveAllIssueoData, len(issueData))
	for i, issues := range issueData {
		res[i] = CveAllIssueoData{
			CveNum:    issues.CveNum,
			IssueNum:  issues.IssueNum,
			Version:   issues.OwnedVersion,
			Component: issues.OwnedComponent,
			Create:    issues.CreateTime.Format("2006-01-02 15:04:05"),
		}
	}
	resp["body"] = res

	u.success(resp)
}

// DataForZeroDaySystem resp dta
type DataForZeroDaySystem struct {
	models.AllIssueForZeroDay
	IssueUrl      string   `json:"issue_url"`
	RpmPublicTime string   `json:"rpm_public_time"`
	Patches       []string `json:"patches"`
	Reference     []string `json:"reference"`
}

func (d *DataForZeroDaySystem) issueUrl() string {
	return fmt.Sprintf(url, d.Owner, d.Repo, d.IssueNum)
}

func (d *DataForZeroDaySystem) getRpmPublicTime() string {
	var rpmPub string
	if len(d.RpmReleaseTime) > 0 {
		rpmPub = d.RpmReleaseTime
	} else {
		rpmPub = d.SaReleaseTime
	}

	return rpmPub
}

// ForZeroDaySystem all issue api for zero day system
func (u *CveAllIssueController) ForZeroDaySystem() {
	resp := make(map[string]interface{})

	currentPage, err := u.GetInt("currentPage", 1)
	if err != nil {
		u.paramError(resp)
	}

	const maxSize = 100
	pageSize, err := u.GetInt("pageSize", maxSize)
	if err != nil {
		u.paramError(resp)
	}

	communityFlag, flagErr := u.GetInt("communityFlag", 0)
	if flagErr != nil {
		communityFlag = 0
	}

	startTime := u.GetString("startTime", "")

	issueData, err := models.QueryAllIssueForZeroDay(currentPage, pageSize, communityFlag, startTime)
	if err != nil {
		logs.Error("QueryCveAllIssueData failed: ", err)
		resp["errno"] = errcode.RecodeDbErr
		resp["errmsg"] = errcode.RecodeText(errcode.RecodeDbErr)
		u.response(resp)
	}

	data := make([]DataForZeroDaySystem, len(issueData))
	for i := range issueData {
		item := DataForZeroDaySystem{AllIssueForZeroDay: issueData[i]}
		item.RpmPublicTime = item.getRpmPublicTime()
		item.IssueUrl = item.issueUrl()
		item.Patches = models.QueryPatches(item.CveNum)
		item.Reference = models.QueryReference(item.CveNum)

		data[i] = item
	}

	resp["body"] = data
	u.success(resp)
}

type KanbanCveAllIssueController struct {
	BaseController
}

type KanbanCveAllIssueoData struct {
	IssueNum            string  `json:"issue_id"`
	CveNum              string  `json:"CVE_num"`
	OpeneulerScore      float64 `json:"openeuler_score"`
	NvdScore            float64 `json:"NVD_score"`
	Branch              string  `json:"milestone"`
	CveLevel            string  `json:"CVE_level"`
	PlanClosedTime      string  `json:"plan_closed_time"`
	CvePublicTime       string  `json:"CVE_public_time"`
	CveVtopicPublicTime string  `json:"CVE_vtopic_public_time"`
	CveVtopicRecTime    string  `json:"CVE_vtopic_rec_time"`
	RpmPublicTime       string  `json:"rpm_public_time"`
	SaPublicTime        string  `json:"SA_public_time"`
}

// @Title Obtain all cve data and provide operation kanban for use
// @Description get issue
// @Param	pageSize 1,currentPage 10, updatetime nil
// @Success 200 {object} CveAllIssueoData
// @Failure 403 :status is err
// @router / [get]
func (u *KanbanCveAllIssueController) Get() {
	req := u.Ctx.Request
	addr := req.RemoteAddr
	logs.Info("Method: ", req.Method, "Client request ip address: ", addr,
		", Header: ", req.Header, ", body: ", req.Body)
	resp := make(map[string]interface{})

	communityFlag, flagErr := u.GetInt("communityFlag", 0)
	if flagErr != nil {
		communityFlag = 0
	}

	currentPage, err := u.GetInt("currentPage", 1)
	if err != nil {
		logs.Error("err: ", err, ", currentPage: ", currentPage)
		u.paramError(resp)
	}
	pageSize, err := u.GetInt("pageSize", 100)
	if err != nil {
		logs.Error("err: ", err, ", pageSize: ", pageSize)
		u.paramError(resp)
	}

	count := models.QueryCveOrgIssueCount(communityFlag)
	if count > 0 {
		resp["total"] = count
		issueData := models.QueryCveOrgIssueData(currentPage, pageSize, communityFlag)
		if len(issueData) > 0 {
			var cid = make([]CveAllIssueoData, len(issueData))
			for i, org := range issueData {
				var irda CveAllIssueoData
				irda.CveNum = org.CveNumber
				irda.IssueNum = org.Number
				issues, isErr := models.QueryCveSingleIssueData(communityFlag, org.RepoPath, org.CveNumber, org.Number)
				if isErr == nil {
					irda.Version = issues.OwnedVersion
					irda.Component = issues.OwnedComponent
					irda.OpeneulerScore = issues.OpeneulerScore
					irda.NvdScore = issues.NvdScore
					irda.Branch = issues.AffectedVersion
					if issues.OpeneulerScore >= 0 {
						irda.CveLevel = models.OpenEulerScoreProc(issues.OpeneulerScore)
					}
					irda.CveVtopicPublicTime = issues.FirstPerTime
					irda.CveVtopicRecTime = issues.FirstGetTime
					irda.PlanClosedTime = taskhandler.CvePlanCloseTime(issues.CreateTime, issues.CveLevel, false, false)
					irda.CvePublicTime = issues.CpublicTime
					if len(issues.RpmReleaseTime) > 2 {
						irda.RpmPublicTime = issues.RpmReleaseTime
					} else {
						irda.RpmPublicTime = issues.SaReleaseTime
					}
					irda.SaPublicTime = issues.SaReleaseTime
				}

				cid[i] = irda
			}
			resp["body"] = cid

			u.success(resp)
		}
	}

	u.noData(resp, nil)
}

type UpdateAffectedBranchController struct {
	BaseController
}

func (u *UpdateAffectedBranchController) Post() {
	branch := u.Ctx.Request.Form.Get("branch")
	cveNum := u.Ctx.Request.Form.Get("cve_num")
	token := u.Ctx.Request.Header.Get("token")

	resp := make(map[string]interface{})
	if token == "" || !models.CheckToken(token) {
		resp["errno"] = errcode.RecodeSessionErr
		resp["errmsg"] = errcode.RecodeText(errcode.RecodeSessionErr)
		u.response(resp)
	}

	affectedBranchs := beego.AppConfig.String("cve::affected_branchs")
	affectedBranchsSet := sets.NewString(strings.Split(affectedBranchs, ",")...)
	if !affectedBranchsSet.Has(branch) {
		u.paramError(resp)
	}

	affectedPackages, err := u.GetPackageByBranch(branch)
	if err != nil {
		u.paramError(resp)
	}

	issues, err := models.QueryIssueToUpdateAffected(cveNum, affectedPackages)
	if err != nil {
		resp["errno"] = errcode.RecodeDbErr
		resp["errmsg"] = errcode.RecodeText(errcode.RecodeDbErr)
		u.response(resp)
	}

	go func() {
		defer func() {
			if e := recover(); e != nil {
				updateAffectedLog(fmt.Sprintf("panic %s", e), nil)
			}
		}()

		o := orm.NewOrm()
		for _, issue := range issues {
			if issue.HasBranch(branch) {
				continue
			}

			appendStr := fmt.Sprintf(",%s:", branch)
			issue.AffectedVersion += appendStr
			issue.AbiVersion += appendStr
			if issue.IsIssueWithAnalysisVersion() {
				issue.AnalysisVersion += appendStr
			}

			var vuln models.VulnCenter
			err = models.GetVulnCenterByCVEID(&vuln, issue.CveId)
			if err != nil {
				updateAffectedLog("get vuln", err)
				continue
			}

			_, err = taskhandler.UpdateIssueToGit("", "", vuln.PackName, vuln, issue, 1)
			if err != nil {
				updateAffectedLog("update gitee", err)
				continue
			}

			err = models.UpdateIssueTemplate(&issue, "AffectedVersion", "AbiVersion", "AnalysisVersion")
			if err != nil {
				updateAffectedLog("update issue template", err)
				continue
			}

			o.Insert(&models.UpdatedAffectedBranch{
				IssueNum:   issue.IssueNum,
				CveNum:     issue.CveNum,
				Package:    vuln.PackName,
				Branch:     branch,
				CreateTime: time.Now().Format(common.DATE_FORMAT),
				UpdateTime: time.Now().Format(common.DATE_FORMAT),
			})

			time.Sleep(time.Second * 1)
		}
	}()

	resp["errno"] = errcode.RecodeOk
	resp["errmsg"] = errcode.RecodeText(errcode.RecodeOk)
	u.response(resp)
}

func updateAffectedLog(t string, err error) {
	logs.Error("update affected, %s err: %s", t, err.Error())
}

func (u *UpdateAffectedBranchController) Get() {
	resp := make(map[string]interface{})

	page, err := u.GetInt("page", 1)
	if err != nil {
		u.paramError(resp)
	}

	pageSize, err := u.GetInt("page_size", 10)
	if err != nil {
		u.paramError(resp)
	}

	data, err := models.QueryUpdatedAffected(page, pageSize)
	if err != nil {
		resp["errno"] = errcode.RecodeDbErr
		resp["errmsg"] = errcode.RecodeText(errcode.RecodeDbErr)
		u.response(resp)
	}

	resp["body"] = data
	u.success(resp)
}

// GetPackageByBranch 根据分支获取包列表，返回包名数组和错误信息。
func (u *UpdateAffectedBranchController) GetPackageByBranch(branch string) ([]string, error) {
	data, err := models.QueryPackageByBranch(branch)
	if err != nil {
		return nil, err
	}

	var packages []string
	for _, v := range data {
		split := strings.Split(v.Branchs, ",")
		for _, b := range split {
			if b == branch {
				packages = append(packages, v.PackageName)
			}
		}
	}

	return packages, nil
}
